
import { Kline, Quant } from "../lib/quant";
import { DataHandler, MergeData } from "./DataHandler";
import { KLineDTO } from "../module/kline-history/KLineHistory.entity";
import { CustomIndicators, MixIndicator } from "./MixIndicator";
import { PERIOD_INTERVAL_MAP } from "../utils";
import { omit } from "lodash";
import { findPeaksAndValleys } from "./util";

export interface KlineMark {
  symbol: string;
  timestamp: number;
  close: number;
  action?: string;
  label?: number;
  label_text?: string;
}

export interface Context {
  sell: Kline[];
  buy: Kline[];
  index: number;
}

export interface KlineWithVector extends MergeData {
  feature_vector_detail: CustomIndicators;
}
export const removeKlienKeys = [
  'isMock',
  'asks',
  'bids',
  'buy',
  'sell'
]

export const removeFeatureVectorDetailKeys: Array<keyof CustomIndicators>  = [
  'statistics_4h_max',
  'statistics_4h_min',
  'statistics_1d_max',
  'statistics_1d_min',
  'vector_4h',
  'vector_1d',
  // 'vector_stort_4h',
  // 'vector_stort_1d'
];

/**
 * 根据历史回溯优化参数
 */
export class FindTradingPoints {
  symbol: string;
  dataHandler: DataHandler;
  quant: Quant;
  feature_vector_list: KlineWithVector[] = [];
  context: Context = { buy: [], sell: [], index: 0 };
  period: "15m" | "4h" | "1d";
  constructor(
    symbol: string,
    period: "15m" | "4h" | "1d",
    history?: KLineDTO[]
  ) {
    this.symbol = symbol;
    this.period = period;
    const dataHander = new DataHandler('mock');
    const mixIndicator = new MixIndicator({isMock: true});
    mixIndicator.onMixIndicator(( bar, indicators, feature_vector_detail) => {
      this.feature_vector_list.push({
        ...omit(bar, removeKlienKeys) as any,
        feature_vector_detail: omit(feature_vector_detail, removeFeatureVectorDetailKeys) as CustomIndicators,
      })
    });

    dataHander.on('interval:15m', (data) => {
      mixIndicator.push(data, '15m');
    });
  
    dataHander.on('interval:4h', (data) => {
      mixIndicator.push(data, '4h');
    });
  
    dataHander.on('interval:1d', (data) => {
      mixIndicator.push(data, '1d');
    });
  
    dataHander.on('update:4h', (data) => {
      mixIndicator.update(data, '4h');
    });
    dataHander.on('update:1d', (data) => {
      mixIndicator.update(data, '1d');
    });

    history.forEach(item => {
      dataHander.handleHistory(item)
    });
  }

  analyse(currentIndex: number): 'peak' | 'valley' | void{
    this.feature_vector_list[currentIndex];
    return findPeaksAndValleys(this.feature_vector_list, currentIndex);
  }

  findTradingPoints(target_profit = 0.35, maxStep = (60 * 24 * 1.5)): KlineWithVector[] {
    const feature_vector_list = this.feature_vector_list;
    const tradingPoints: KlineWithVector[] = [];
    // const peaks: KlineWithVector[] = []; // 峰顶
    // const valleys: KlineWithVector[] = []; // 山谷

    const small_step = maxStep / 3 | 0;
    const end_dis = maxStep;

    let currentIndex = end_dis;
    while(currentIndex < feature_vector_list.length - end_dis) {
      const type = this.analyse(currentIndex)
      if (type) {
      
        const label = type === 'peak' ? -1 : 1;
        const last = tradingPoints[tradingPoints.length - 1];
       
        const checkPointResult = checkPoint(feature_vector_list, type, currentIndex, {
          maxStep: maxStep,
          // maxStep: 10,
          targetProfit: target_profit
        })

        if (checkPointResult.profit) {
          // 如果与最后一个方向一样，则需要判断是否合理
          if (last && last.label === label) {
            if (type === 'peak' 
            && feature_vector_list[checkPointResult.nextIndex].close < last.close
            && (feature_vector_list[checkPointResult.nextIndex].timestamp - last.timestamp) < PERIOD_INTERVAL_MAP['1d'] * 15
            ) {
              currentIndex = currentIndex + small_step;

              continue;
            } else if (type === 'valley'
            && feature_vector_list[checkPointResult.nextIndex].close > last.close
            && (feature_vector_list[checkPointResult.nextIndex].timestamp - last.timestamp) < PERIOD_INTERVAL_MAP['1d'] * 30
            ) {
              currentIndex = currentIndex + small_step;
              continue;
            }
          }
          currentIndex = checkPointResult.nextIndex;
          tradingPoints.push({
            ...feature_vector_list[currentIndex],
            label,
          });
        }
        currentIndex = currentIndex + small_step;
      } else {
        currentIndex++;
      }
    }
    return tradingPoints
  }

  updateContext(type: "buy" | "sell", kline: Kline) {
    this.context[type].push(kline);
    if (this.context[type].length > 5) {
      this.context[type].shift();
    }
  }
}



export function checkPoint(
  history: Kline[],
  type: "peak" | "valley",
  cursorIndex: number, 
  optios: {
    maxStep: number;
    targetProfit: number;
  } = {targetProfit: 0.1, maxStep: 60 * 48 / 15}
) {
  const result = {
    cursorMaxProfit: 0,
    profit: false,
    nextIndex: cursorIndex,
    index: 0,
  }
  if (!Array.isArray(history) || history[cursorIndex] == undefined) {
    return result;
  }
  const targetProfit = optios.targetProfit; // 目标收益率
  let maxStep = optios.maxStep;
  const cursor = history[cursorIndex];
  let index = 1;

  while(result.nextIndex < history.length - 1 && !result.profit) {
    const nextIndex = cursorIndex + index;
    
    if (history[nextIndex] === undefined) {
      break;
    }
    const prePoint = history[result.nextIndex];
    // const nextType = this.analyse(nextIndex);
    const { close } = history[nextIndex];
    const profit = (close - prePoint.close) / prePoint.close;
    if (Math.abs(profit) > result.cursorMaxProfit) {
      result.cursorMaxProfit = profit;
    }
    if (type === 'peak') {
      // 可能后面的趋势会更差
      if (close < cursor.close && index > maxStep) {
        break
      }
      if (profit < -targetProfit) {
        result.profit = true;
        result.cursorMaxProfit = profit;
      }
      // 有更好的卖点，更新索引
      if (close > prePoint.close) {
        result.nextIndex = cursorIndex + index;
        // 奖励探索步数
        maxStep += optios.maxStep;
      }
    } else if (type === 'valley') {
      // 可能后面的趋势会更差
      if (close > cursor.close && index > maxStep) {
        break
      }
      if (profit > targetProfit) {
        result.profit = true;
        result.cursorMaxProfit = profit;
      }
      // 有更好的买点，更新索引
      if (close < prePoint.close) {
        result.nextIndex = cursorIndex + index;
        // 奖励探索步数
        maxStep += optios.maxStep;
      }
    }
    index++;
  }
  result.index = index;
  return result;
}
